﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Windows.Forms;
using Meid_Finder.Properties;

namespace Meid_Finder
{
	public partial class formMeidFinder : Form
	{
		private int EsnLength = 6;
		private int foundCounterLimit = 10;
		private int numberCores = 0;
		private static int foundCounter = 0;
		private static string desiredEsn = string.Empty;

		private static List<MeidSearch> Searches { get; set; }

		public formMeidFinder()
		{
			InitializeComponent();
			EsnLength = Settings.Default.EsnLength;

			for (int i = 1; i <= foundCounterLimit; i++)
				cbxNumToGen.Items.Add(i);
			cbxNumToGen.SelectedItem = 5;
			foundCounterLimit = (int)cbxNumToGen.SelectedItem;

			Searches = new List<MeidSearch>();

			foreach (var item in new ManagementObjectSearcher("Select * from Win32_Processor").Get())
			{
				numberCores += int.Parse(item["NumberOfCores"].ToString());
			}
			txtNumberCores.Text = numberCores.ToString();
		}

		/// <summary>
		/// Generate SHA-1 hash from string of HEX values
		/// </summary>
		/// <param name="hexString"></param>
		/// <returns></returns>
		private static string GenerateHashCode(string hexString)
		{
			string shaHash = string.Empty;
			try
			{
				// Convert the string of HEX values to an array of Byte values
				byte[] bytes = StringToByteArray(hexString);
				SHA1 sha1 = SHA1.Create();
				// Generate the SHA-1 hash as an array of Byte values
				byte[] byteResult = sha1.ComputeHash(bytes);
				// Convert the byte array hash to HEX values. Also remove "-" between each HEX value
				shaHash = BitConverter.ToString(byteResult).Replace("-", String.Empty);
				bytes = null;
				byteResult = null;
				sha1.Clear();
				sha1.Dispose();
			}
			catch (Exception ex)
			{
				string strerr = "Error in HashCode : " + ex.Message;
			}
			return shaHash;
		}

		/// <summary>
		/// Convert a string of HEX data to an array of bytes
		/// Found more effecient solution. Reference: http://stackoverflow.com/questions/311165/how-do-you-convert-byte-array-to-hexadecimal-string-and-vice-versa
		/// </summary>
		/// <param name="hex">string of HEX such as A10000009296F2</param>
		/// <returns>array of bytes representing the HEX data</returns>
		private static byte[] StringToByteArray(String hex)
		{
			// divide by two because 1 byte = 2 hex characters
			int NumberChars = hex.Length / 2;
			// create array of bytes
			byte[] bytes = new byte[NumberChars];
			StringReader sr = new StringReader(hex);
			// for each slot in the bytes array convert two HEX characters from the input
			for (int i = 0; i < NumberChars; i++)
				bytes[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
			sr.Dispose();
			sr.Close();
			return bytes;
		}

		/// <summary>
		/// Check if a string contains only HEX valid values
		/// </summary>
		/// <param name="test"></param>
		/// <returns>true if string is HEX only values; otherwise false</returns>
		private static bool OnlyHexInString(string test)
		{
			// For C-style hex notation (0xFF) you can use @"\A\b(0[xX])?[0-9a-fA-F]+\b\Z"
			return System.Text.RegularExpressions.Regex.IsMatch(test, @"\A\b[0-9a-fA-F]+\b\Z");
		}

		/// <summary>
		/// Generate MEID DEC value from the MEID HEX value
		/// </summary>
		/// <param name="hexVal"></param>
		/// <returns></returns>
		private string GenerateMeidDec(long hexVal)
		{
			// calculate MEID DEC value
			string start = Convert.ToInt64(hexVal.ToString("X").Substring(0, 8), 16).ToString();
			string end = Convert.ToInt64(hexVal.ToString("X").Substring(8, 6), 16).ToString().PadLeft(8, '0');
			return start + end;
		}

		/// <summary>
		/// Validate the form to make sure all necessary values have been provided
		/// </summary>
		/// <returns>return true if the form is complete</returns>
		private bool ValidateForm()
		{
			// check if ESN input is valid
			if (string.IsNullOrWhiteSpace(txtEsn.Text) || string.IsNullOrEmpty(txtEsn.Text) || !OnlyHexInString(txtEsn.Text))
			{
				MessageBox.Show("The ESN value contains non-HEX values", "Non ESN HEX Value", MessageBoxButtons.OK, MessageBoxIcon.Error);
				txtEsn.Focus();
				txtEsn.SelectAll();
				return false;
			}

			//check if a starting option has been selected
			if (!btnA0.Checked && !btnA1.Checked && !btnCustom.Checked && !btnUserCustom.Checked)
			{
				MessageBox.Show("Select A0, A1, or Custom", "Starting Option Not Selected", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}

			return true;
		}

		/// <summary>
		/// Driving method for asynchronous searching for matches to the desired ESN value. 
		/// </summary>
		public async void SearchPrepAsync()
		{
			// Create new stopwatch
			Stopwatch stopwatch = new Stopwatch();
			// Begin timing
			stopwatch.Start();
			txtStopwatch.Text = string.Empty;
			txtAllDone.Text = string.Empty;

			lblProcessing.Visible = true;

			// only perform this at the start of each new search
			if (Searches.Count == 0)
			{
				long[,] hexVals = new long[1, 2] { { 0xA0000000000000, 0xAFFFFFFFFFFFFF } };

				if (btnA0.Checked)
				{
					hexVals = new long[1, 2] { { 0xA0000000000000, 0xAFFFFFFFFFFFFF } };
				}
				else if (btnA1.Checked)
				{
					hexVals = new long[1, 2] { { 0xA1000000000000, 0xAFFFFFFFFFFFFF } };
				}
				else if (btnCustom.Checked)
				{
					hexVals = new long[3, 2] {  { 0xA0000000000000, 0xA0000001000000 },
												{ 0xA0000002000000, 0xA0000003000000 },
												{ 0xA100000F000000, 0xAFFFFFFFFFFFFF }};
				}
				else if (btnUserCustom.Checked)
				{
					long start = Int64.Parse(txtUserCustomStart.Text, System.Globalization.NumberStyles.HexNumber);
					long end = Int64.Parse(txtUserCustomEnd.Text, System.Globalization.NumberStyles.HexNumber);
					hexVals = new long[1, 2] { { start, end } };
				}

				/*
				 * if counterlimit = 5
				 * 
				 * { 0xA0000000000000, 0xA0000001000000 }
				 *		0x00000001000000 / 5 = 0x00000000333333 break searches into chunks of this amount
				 *			search each chunk with 2 threads. one counts even, the other odd
				 *
				 *			0xA0000000000000 - 0xA0000000333333
				 *				this equals 2 searches:
				 *					0xA0000000000000, 0xA0000000000002, 0xA0000000000004.....0xA0000000333332
				 *					0xA0000000000001, 0xA0000000000003, 0xA0000000000005.....0xA0000000333333
				 *			0xA0000000333333 - 0xA0000000666666
				 *			0xA0000000666666 - 0xA0000000999999
				 *			0xA0000000CCCCCC - 0xA0000000FFFFFF
				 *		
				 * { 0xA0000002000000, 0xA0000003000000 }
				 * { 0xA100000F000000, 0xAFFFFFFFFFFFFF }
				 */

				//for each search range. hexVals.GetLength(0) is the x in long[x,y] for the number of 'rows'
				for (int j = 0; j < hexVals.GetLength(0); j++)
				{
					// (EndValue - StartValue) / NumberMeidToFind = Number of values per search
					// (10000 - 2000) / 5 = 8000 / 5 = 1600
					long spacer = (hexVals[j, 1] - hexVals[j, 0]) / foundCounterLimit;
					// 2000
					long start = hexVals[j, 0];
					// 2000 + 1600 = 3600
					long end = start + spacer;

					// create search ranges
					for (int m = 0; m <= foundCounterLimit; m++)
					{
						Searches.Add(new MeidSearch()
						{
							StartNumber = start,
							EndNumber = end,
							IsCompleted = false
						});

						Searches.Add(new MeidSearch()
						{
							StartNumber = start + 1,
							EndNumber = end + 1,
							IsCompleted = false
						});

						// 2000 + 1600 = 3600
						start = start + spacer;
						// 3601 + 1600 = 5201
						end = start + spacer + 1;
					}
				}
			}

			// perform search in new worker thread so UI is responseive
			var results = await SearchAsync();

			// Stop timing
			stopwatch.Stop();
			// Write result
			txtStopwatch.Text = stopwatch.Elapsed.ToString();

			bool isDone = Searches.Exists(s => s.IsCompleted == false);
			if (!isDone)
			{
				txtAllDone.Text = "yes";
				btnFindMore.Enabled = true;
			}
			else
			{
				txtAllDone.Text = "no";
			}

			// Enable the Next 5 button
			btnFindMore.Enabled = true;

			FlashWindow(this.Handle, false);
		}

		[DllImport("user32.dll")]
		static extern bool FlashWindow(IntPtr hwnd, bool bInvert);

		/// <summary>
		/// Performs asynchonous parallel calls to GetMeidAsync to find matches to the desired ESN value.
		/// As the matches are found they are added to the result grid and the search continues until a predefined number of results are found
		/// </summary>
		/// <returns>List of MeidResult class objects representing the matches found</returns>
		private async Task<List<MeidResult>> SearchAsync()
		{
			List<MeidResult> results = new List<MeidResult>();

			int s = 0;

			// loop through the Searches[] until all are completed OR we find the counter limit
			do
			{
				// if this search is completed then do not perform it
				if (!Searches[s].IsCompleted)
				{
					List<Task<MeidResult>> tasks = new List<Task<MeidResult>>();
					tasks.Add(GetMeidsAsync(Searches[s]));

					if ((s + 1) < Searches.Count)
						if (!Searches[s + 1].IsCompleted)
							tasks.Add(GetMeidsAsync(Searches[s + 1]));

					try
					{
						MeidResult[] taskResult = await Task.WhenAll(tasks);

						for (int i = 0; i < taskResult.Length; i++)
						{
							if (taskResult[i] != null)
							{
								// if a result is found then add to results and increment the found counter
								if (!string.IsNullOrEmpty(taskResult[i].Dec))
								{
									results.Add(taskResult[i]);
									foundCounter++;
									gridMeidResults.Rows.Add();
									gridMeidResults[0, gridMeidResults.RowCount - 1].Value = gridMeidResults.RowCount - 1;
									gridMeidResults[1, gridMeidResults.RowCount - 1].Value = taskResult[i].Dec;
									gridMeidResults[2, gridMeidResults.RowCount - 1].Value = taskResult[i].Hex;
								}

								Searches[s + i].StartNumber = taskResult[i].Counter + 2;
								// check if we are at the end of the given search, if so then do not perform this search again
								if (Searches[s + i].StartNumber >= Searches[s].EndNumber)
								{
									Searches[s + i].IsCompleted = true;
								}
							}
						}
					}
					catch(Exception exc)
					{
						foreach(var faulted in tasks.Where(t => t.IsFaulted))
						{
							// work with faulted and faulted.Exception
						}
					}
				}
				else
				{
					s++;
				}
			} while (s < Searches.Count && foundCounter < foundCounterLimit);
			foundCounter = 0;
			return results;
		}

		/// <summary>
		/// Method to generate SHA-1 hash codes inside a range of values until a matching value is genereated equal to the desired SHA-1 (desired ESN)
		/// </summary>
		/// <param name="searchStart">MeidSearch class object which defines the range of numbers to search within</param>
		/// <returns>MeidResult class object which represents a match OR no match (must look at properties of the object to know)</returns>
		private Task<MeidResult> GetMeidsAsync(MeidSearch searchStart)
		{
			return Task.Run(() =>
			{
				MeidResult result = new MeidResult();
				long start = searchStart.StartNumber;
				long end = searchStart.EndNumber;

				while (start <= end)
				{
					// use ToString("X") to convert int to hex
					string tmp = GenerateHashCode(start.ToString("X"));
					string tmpSub = tmp.Substring(tmp.Length - EsnLength, EsnLength);

					if (tmpSub.Equals(desiredEsn))
					{
						// calculate MEID DEC value
						result.Dec = GenerateMeidDec(start);
						result.Hex = start.ToString("X");
						// reference to know the 'long' value that generated the result and where to continue from to find the next match
						result.Counter = start;
						break;
					}
					tmp = string.Empty;
					tmpSub = string.Empty;
					start = start + 2;
				}

				return result;
			});
		}

		/// <summary>
		/// Called when the Find button is clicked
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnFind_Click(object sender, EventArgs e)
		{
			if (ValidateForm())
			{
				// disable the button btnFind
				btnFind.Enabled = false;
				txtEsn.Enabled = false;
				btnA0.Enabled = false;
				btnA1.Enabled = false;
				btnCustom.Enabled = false;
				btnUserCustom.Enabled = false;
				txtCustomerName.Enabled = false;


				if (btnUserCustom.Checked)
				{
					txtUserCustomStart.Enabled = false;
					txtUserCustomEnd.Enabled = false;
				}

				desiredEsn = txtEsn.Text;

				//txtCurrentMeidValue.Text = Search();
				SearchPrepAsync();
			}
		}

		/// <summary>
		/// Called when the Another 5 button is clicked
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnFindMore_Click(object sender, EventArgs e)
		{
			gridMeidResults.Rows.Clear();
			SearchPrepAsync();
		}

		/// <summary>
		/// Called when the New ESN button is clicked
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnNewEsn_Click(object sender, EventArgs e)
		{
			txtEsn.Enabled = true;
			btnA0.Enabled = true;
			btnA1.Enabled = true;
			btnCustom.Enabled = true;
			btnUserCustom.Enabled = true;
			btnFindMore.Enabled = false;
			btnNewEsn.Enabled = true;
			btnFind.Enabled = true;
			txtUserCustomStart.Enabled = true;
			txtUserCustomEnd.Enabled = true;
			txtCustomerName.Enabled = true;

			txtEsn.Text = string.Empty;
			gridMeidResults.Rows.Clear();
			btnA0.Checked = false;
			btnA1.Checked = false;
			btnCustom.Checked = false;
			txtStopwatch.Text = string.Empty;
			txtAllDone.Text = string.Empty;
			txtCustomerName.Text = string.Empty;

			lblUserCustomStart.Visible = false;
			lblUserCustomEnd.Visible = false;
			txtUserCustomStart.Visible = false;
			txtUserCustomEnd.Visible = false;
			txtUserCustomStart.Text = string.Empty;
			txtUserCustomEnd.Text = string.Empty;

			//set focus back to the Desired ESN input
			txtEsn.Focus();
			txtEsn.SelectAll();
		}

		/// <summary>
		/// Called when the Quit button is clicked
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnQuit_Click(object sender, EventArgs e)
		{
			this.Close();
		}

		private void cbxNumToGen_SelectedIndexChanged(object sender, EventArgs e)
		{
			foundCounterLimit = (int)cbxNumToGen.SelectedItem;
		}

		private void btnUserCustom_CheckedChanged(object sender, EventArgs e)
		{
			if (btnUserCustom.Checked)
			{
				lblUserCustomStart.Visible = true;
				lblUserCustomEnd.Visible = true;
				txtUserCustomStart.Visible = true;
				txtUserCustomEnd.Visible = true;
			}
			else
			{
				lblUserCustomStart.Visible = false;
				lblUserCustomEnd.Visible = false;
				txtUserCustomStart.Visible = false;
				txtUserCustomEnd.Visible = false;

				txtUserCustomStart.Text = string.Empty;
				txtUserCustomEnd.Text = string.Empty;
			}
		}

		private void btnA0_CheckedChanged(object sender, EventArgs e)
		{

		}

		private void btnA1_CheckedChanged(object sender, EventArgs e)
		{

		}

		private void btnCustom_CheckedChanged(object sender, EventArgs e)
		{

		}

		private void formMeidFinder_Load(object sender, EventArgs e)
		{

		}

		private void txtEsn_TextChanged(object sender, EventArgs e)
		{

		}

		private void lblAbout_Click(object sender, EventArgs e)
		{

		}
	}

	class MeidSearch
	{
		/// <summary>
		/// Number to (re)start this search on
		/// </summary>
		public long StartNumber { get; set; }

		/// <summary>
		/// Number to end this search on
		/// </summary>
		public long EndNumber { get; set; }

		/// <summary>
		/// Is this search completed?
		/// </summary>
		public bool IsCompleted { get; set; }

		/// <summary>
		/// Number of Meid to find in this search
		/// </summary>
		public int MeidCountToFind { get; set; }

		public MeidSearch()
		{
		}

		public MeidSearch(long Start, long End)
		{
			StartNumber = Start;
			EndNumber = End;
		}
	}

	public class MeidResult
	{
		public string Dec { get; set; }
		public string Hex { get; set; }
		public long Counter { get; set; }

		public MeidResult()
		{
		}

		public MeidResult(string dec, string hex, long counter)
		{
			Dec = dec;
			Hex = hex;
			Counter = counter;
		}
	}
}
